﻿using System;
using System.Collections.Generic;
using Transformable_Engine_v2.Engine.Animation;
using Transformable_Engine_v2.Engine.Animation.PositionAnimation;
using Transformable_Engine_v2.Engine.Animation.Queue;
using Transformable_Engine_v2.Engine.Global;
using Transformable_Engine_v2.Engine.Helper;
using Transformable_Engine_v2.Engine.Input;
using Transformable_Engine_v2.Engine.Time;
using Transformable_Engine_v2.Engine.Transformable_Math.Geometry;

namespace Transformable_Engine_v2.Engine.GameObjects.Containers
{
    public class Container : IContainer, IUnloadable, IPositionAnimatable
    {
        public delegate void OnMouseHandler(TE_Vector2 position);

        private readonly List<Timer> _timers;
        private TE_Rectangle _boundingBox;

        private bool _hoverState;
        private bool _isAlive;

        private bool _isClickEnabled;

        /// <summary>
        ///     Position of the container, relative to its parent
        /// </summary>
        protected TE_Vector2 _position;

        /// <summary>
        ///     Creates a new instance of container
        /// </summary>
        public Container()
        {
            _hoverState = false;
            Children = new List<Container>();
            Position = new TE_Vector2();
            _isAlive = true;
            Animations = new List<BaseAnimation>();
            Visible = true;
            _isClickEnabled = false;
            _timers = new List<Timer>();
        }

        /// <summary>
        ///     Creates a new instance of container
        /// </summary>
        /// <param name="positionX">Position X (gridbound)</param>
        /// <param name="positionY">Position Y (gridbound)</param>
        public Container(float positionX, float positionY)
            : this()
        {
            PositionGridBound = new TE_Vector2(positionX, positionY);
        }

        /// <summary>
        ///     Creates a new instance of container
        /// </summary>
        /// <param name="position">Position of the container, gridbound</param>
        public Container(TE_Vector2 position)
            : this()
        {
            PositionGridBound = position;
        }

        /// <summary>
        ///     Absolute position of the container
        /// </summary>
        public virtual TE_Vector2 AbsolutePosition { get; protected set; }

        /// <summary>
        ///     Bounding box of the object, for mouse collision
        /// </summary>
        public TE_Rectangle BoundingBox
        {
            get { return _boundingBox; }
            set
            {
                if (!_isClickEnabled)
                    MouseHandler.OnMouseClick += VerifyMouseClick;
                _isClickEnabled = true;
                _boundingBox = value;
            }
        }

        /// <summary>
        ///     Absolute bounding box of the object
        /// </summary>
        public TE_Rectangle AbsoluteBoundingBox { get; private set; }

        /// <summary>
        ///     Visibility of the object
        /// </summary>
        public bool Visible { get; set; }

        /// <summary>
        ///     Position of the container, relative to its parent
        /// </summary>
        public virtual TE_Vector2 Position
        {
            get { return _position; }
            set
            {
                _position = value;
                SetAbsolutePosition();
            }
        }

        /// <summary>
        ///     Position of the container, relative to its parent (gridbound)
        /// </summary>
        public TE_Vector2 PositionGridBound
        {
            set { Position = value.ToGridbound(); }
            get { return Position.AsGridBound(); }
        }

        /// <summary>
        ///     Children objects of the container
        /// </summary>
        public List<Container> Children { get; set; }

        /// <summary>
        ///     Animations affecting this control
        /// </summary>
        public List<BaseAnimation> Animations { get; private set; }

        /// <summary>
        ///     Parent object of the container
        /// </summary>
        public Container Parent { get; set; }

        /// <summary>
        ///     Load the needed content for this container to properly function
        /// </summary>
        public virtual void LoadContent()
        {
        }

        /// <summary>
        ///     Free resources used by the container
        /// </summary>
        public virtual void UnloadContent()
        {
            if (_isClickEnabled)
                MouseHandler.OnMouseClick -= VerifyMouseClick;
        }

        /// <summary>
        ///     Initialize the container
        /// </summary>
        public virtual void Initialize()
        {
        }

        /// <summary>
        ///     Update the container object and its children
        /// </summary>
        public virtual void Update(TimeSpan gameTime)
        {
            for (int i = Children.Count - 1; i >= 0; i--)
            {
                try
                {
                    Children[i].Update(gameTime);
                }
                catch (Exception e)
                {
                    Log.LogError("Children update failed:" + e.Message);
                }
            }

            for (int i = Animations.Count - 1; i >= 0; i--)
            {
                try
                {
                    Animations[i].Update(gameTime);
                }
                catch (Exception e)
                {
                    Log.LogError("Animation update failed " + e.Message);
                }
            }

            if (BoundingBox != null && Game.Options.MouseEnabled)
            {
                if (CollisionHelper.Intersects(MouseHandler.MousePosition, AbsoluteBoundingBox))
                {
                    if (_hoverState)
                        OnMouseHover(MouseHandler.MousePosition);
                    else
                        OnMouseEnter(MouseHandler.MousePosition);
                    _hoverState = true;
                }
                else
                {
                    if (_hoverState)
                        OnMouseLeave(MouseHandler.MousePosition);

                    _hoverState = false;
                }
            }
        }

        /// <summary>
        ///     Update the physics of the container and its children
        /// </summary>
        public virtual void FixedStepUpdate(TimeSpan gameTime)
        {
            for (int i = _timers.Count - 1; i >= 0; i--)
            {
                _timers[i].FixedStepUpdate(gameTime);
            }

            for (int i = Children.Count - 1; i >= 0; i--)
            {
                Children[i].FixedStepUpdate(gameTime);

                if (Children.Count > 0 && !Children[i].IsAlive())
                {
                    Children[i].UnloadContent();
                    Children.RemoveAt(i); 
                }
            }

            for (int i = Animations.Count - 1; i >= 0; i--)
            {
                Animations[i].FixedStepUpdate(gameTime);

                if (!Animations[i].IsAlive())
                {
                    Animations[i].UnloadContent();
                    Animations.RemoveAt(i);
                }
            }
        }

        /// <summary>
        ///     Draw the container and all of its children
        /// </summary>
        public virtual void Draw()
        {
            try
            {
                if (Visible)
                    for (int i = 0; i < Children.Count; i++)
                        Children[i].Draw();
            }
            catch (Exception e)
            {
                Log.LogError("Draw failed: " + e.Message);
            }
        }

        /// <summary>
        ///     Method called when the game is forcefully shut down
        /// </summary>
        public virtual void Close()
        {
        }

        /// <summary>
        ///     Gets the position
        /// </summary>
        /// <returns></returns>
        public TE_Vector2 GetPosition()
        {
            return Position;
        }

        /// <summary>
        ///     Adds to the current position
        /// </summary>
        /// <param name="position"></param>
        public void AddToPosition(TE_Vector2 position)
        {
            Position += position;
        }

        /// <summary>
        ///     Is the container still alive?
        /// </summary>
        /// <returns>IsAlive</returns>
        public bool IsAlive()
        {
            return _isAlive;
        }

        /// <summary>
        ///     Flag for garbage collection
        /// </summary>
        public virtual void Die()
        {
            _isAlive = false;
        }

        public event OnMouseHandler OnEnter;
        public event OnMouseHandler OnHover;
        public event OnMouseHandler OnLeave;
        public event OnMouseHandler OnClick;

        private void VerifyMouseClick(TE_Vector2 position)
        {
            if (CollisionHelper.Intersects(AbsoluteBoundingBox, position))
                OnMouseClick(position);
        }

        protected virtual void OnMouseClick(TE_Vector2 position)
        {
            if (OnClick != null)
                OnClick(position);
        }

        /// <summary>
        ///     Calculate the absolute position of the container
        /// </summary>
        public void SetAbsolutePosition()
        {
            if (Parent != null)
                AbsolutePosition = Position + Parent.AbsolutePosition;
            else
                AbsolutePosition = Position;

            if (BoundingBox != null)
                AbsoluteBoundingBox = BoundingBox.ToGridBound() + AbsolutePosition;

            foreach (Container child in Children)
            {
                child.SetAbsolutePosition();
            }
        }

        /// <summary>
        ///     Add a children to the container
        /// </summary>
        /// <param name="children">List of children objects</param>
        public virtual void AddChildren(params Container[] children)
        {
            for (int i = 0; i < children.Length; i++)
            {
                AddChild(children[i]);
            }
        }

        public virtual void AddChild(Container child)
        {
            child.Initialize();
            child.LoadContent();
            child.Parent = this;
            child.SetAbsolutePosition();
            Children.Add(child);
        }

        protected virtual bool SetAnimationAffectedObject(BaseAnimation animation)
        {
            if (animation is PositionAnimation)
            {
                (animation as PositionAnimation).AffectedObject = this;
                return true;
            }

            if (animation is PositionUniformLinearAnimation)
            {
                (animation as PositionUniformLinearAnimation).AffectedObject = this;
                return true;
            }

            if (animation is AnimationQueue)
            {
                AddSubAnimations((animation as AnimationQueue).Animations);
                return true;
            }

            return false;
        }

        /// <summary>
        ///     Set the affected object for sub animations in animations queues
        /// </summary>
        /// <param name="animations">List of animations to add</param>
        private void AddSubAnimations(params BaseAnimation[] animations)
        {
            for (int i = 0; i < animations.Length; i++)
            {
                SetAnimationAffectedObject(animations[i]);

                animations[i].Initialize();
            }
        }

        /// <summary>
        ///     Add an animation to the container
        /// </summary>
        /// <param name="animations">List of animations to add</param>
        public void AddAnimations(params BaseAnimation[] animations)
        {
            for (int i = 0; i < animations.Length; i++)
            {
                if (!SetAnimationAffectedObject(animations[i]))
                {
                    throw new Exception("Animation type not handled for this object");
                }

                animations[i].Initialize();
                Animations.Add(animations[i]);
            }
        }

        public void AddTimers(params Timer[] timers)
        {
            foreach (Timer timer in timers)
            {
                if (timer == null)
                    throw new Exception("Cannot add a null timer");

                _timers.Add(timer);
            }
        }

        /// <summary>
        ///     Destroys all animations affecting the object
        /// </summary>
        public void ClearAnimation()
        {
            for (int i = Animations.Count - 1; i >= 0; i--)
            {
                Animations[i].Die();
                Animations.RemoveAt(i);
            }
        }

        public void ClearAnimation<T>() where T : BaseAnimation
        {
            for (int i = Animations.Count - 1; i >= 0; i--)
            {
                if (Animations[i] is T)
                {
                    Animations[i].Die();
                    Animations.RemoveAt(i);
                }
            }
        }

        protected virtual void OnMouseHover(TE_Vector2 mousePosition)
        {
            if (OnHover != null)
                OnHover(mousePosition);
        }

        protected virtual void OnMouseEnter(TE_Vector2 mousePosition)
        {
            if (OnEnter != null)
                OnEnter(mousePosition);
        }

        protected virtual void OnMouseLeave(TE_Vector2 mousePosition)
        {
            if (OnLeave != null)
                OnLeave(mousePosition);
        }

        /// <summary>
        /// Flag for garbage collection at a later time
        /// </summary>
        public virtual void Die(double timeToLive)
        {
            var deathTimer = new Timer(timeToLive, false, Die);
            deathTimer.Start();
            AddTimers(deathTimer);
        }

        /// <summary>
        /// Call a method at a later time
        /// </summary>
        /// <param name="timeToWait">Time to wait (milliseconds)</param>
        /// <param name="method">Method to call</param>
        public void CallMethod(double timeToWait, Action method)
        {
            var methodTimer = new Timer(timeToWait, false, method);
            methodTimer.Start();
            AddTimers(methodTimer);
        }

        /// <summary>
        /// Call a method periodically
        /// </summary>
        /// <param name="timeToWait">Time to wait (milliseconds)</param>
        /// <param name="method">Method to call</param>
        public void CallMethodEvery(double timeToWait, Action method)
        {
            var methodTimer = new Timer(timeToWait, true, method);
            methodTimer.Start();
            AddTimers(methodTimer);
        }

        public void ClearChildren()
        {
            for (int i = Children.Count - 1; i >= 0; i--)
            {
                Children[i].Die();
            }
        }
    }
}